package com.birdcage.gamemanagers;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;

import com.badlogic.gdx.math.Vector2;
import com.birdcage.goldminer.Game;

public class PhysicsElement
{
    // /Private member
    private boolean m_active = true;
    private GameObject m_parent;
    private Vector2 m_pos = new Vector2(0, 0);
    private Vector2 m_dir = new Vector2(1, 0);
    private float m_velocity = 0;
    private float m_acceleration = 0;
    private float m_maxVelocity = 10000;
    private float m_minVelocity = 0;

    private float m_radius = 15;

    private Method m_collideEnterCallBack;
    private Method m_collideUpdateCallBack;
    private Method m_collideExitCallBack;

    private HashSet<PhysicsElement> m_collidedElement = new HashSet<PhysicsElement>();
    private HashSet<PhysicsElement> m_PreviousCollidedElement = new HashSet<PhysicsElement>();

    // For Debug
    boolean m_visibleDebug = true;
    void DrawDebugElement()
    {
        if (m_visibleDebug)
        {
            Vector2 worldPos = new Vector2();
            worldPos.set(GetWorldPos());
            final float offsetToShowEdge = 1.0f;
            Utility.GetInstance().DrawRect(worldPos.x, worldPos.y, m_radius*2 + offsetToShowEdge, m_radius*2 + offsetToShowEdge, 255, 255, 0, 255);
        }
    }

    // /Construct
    PhysicsElement(GameObject parent)
    {
        m_parent = parent;
        PhysicsManager.GetInstance().AddElement(this);
    }

    public void SetActive(boolean active)
    {
        m_active = active;
    }

    public boolean GetActive()
    {
        boolean result = false;
        // PhysicsElement won't make sense when its parent is not active.
        if (m_parent != null)
        {
            result = m_active && m_parent.GetActive();
        }
        return result;
    }

    public void AddColliedElement(PhysicsElement otherElem)
    {
        m_collidedElement.add(otherElem);
    }

    public void ResolveCollision() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
    {
        Object[] allElements = m_collidedElement.toArray();
        for (int i = 0; i < allElements.length; ++i)
        {
            PhysicsElement currentElem = (PhysicsElement) allElements[i];
            // if the element we get this time is not in previous, it means,
            // it's first time collide with element.
            if (m_PreviousCollidedElement.contains(currentElem))
            {
                if (m_collideUpdateCallBack != null)
                {
                    m_collideUpdateCallBack.invoke(m_parent, currentElem);
                }
                m_PreviousCollidedElement.remove(currentElem);
            } else
            {
                if (m_collideEnterCallBack != null)
                {
                    m_collideEnterCallBack.invoke(m_parent, currentElem); 
                }                
            }
        }
        allElements = m_PreviousCollidedElement.toArray();
        for (int i = 0; i < allElements.length; ++i)
        {
            PhysicsElement currentElem = (PhysicsElement) allElements[i];
            if (m_collideExitCallBack != null)
            {
                m_collideExitCallBack.invoke(m_parent, currentElem);
            }
        }
        m_PreviousCollidedElement.clear();
        m_PreviousCollidedElement.addAll(m_collidedElement) ;
        m_collidedElement.clear();
    }

    public void Update(float deltaTime)
    {
        // / only parent will call this function, so we don't need to check if
        // parent is active, it's for sure true.
        if (m_active)
        {
            // maintain current position by the velocity and direction
            if (GetVelocity() != 0)
            {
                if (GetVelocity() > m_maxVelocity)
                {
                    SetVelocity(m_maxVelocity);
                } else if (GetVelocity() < m_minVelocity)
                {
                    SetVelocity(m_minVelocity);
                }

                Vector2 deltaOffset = new Vector2();
                deltaOffset.set(GetDir());
                deltaOffset.mul(GetVelocity() * deltaTime);
                Vector2 newPos = new Vector2();
                newPos.set(GetRelativePos());
                newPos.add(deltaOffset);

                SetPos(newPos);
            }
            // apply acceleration on velocity.
            if (m_acceleration != 0)
            {
                SetVelocity(GetVelocity() + (m_acceleration * deltaTime));
            }
            if (Game.GetInstance().GetDebugVisible())
            {
                DrawDebugElement();
            }
        }

    }

    public void SetRadius(float m_radius)
    {
        this.m_radius = m_radius;
    }

    public float GetRadius()
    {
        return m_radius;
    }

    public void SetPos(Vector2 m_pos)
    {
        this.m_pos = m_pos;
    }

    public Vector2 GetRelativePos()
    {
        return m_pos.tmp();
    }

    public Vector2 GetWorldPos()
    {
        return m_parent.GetWorldPos();
    }

    public void SetCallBack(Method enterCallBack, Method updateCallBack, Method exitCallBack)
    {
        m_collideEnterCallBack = enterCallBack;
        m_collideUpdateCallBack = updateCallBack;
        m_collideExitCallBack = exitCallBack;
        m_collideEnterCallBack.setAccessible(true);
        m_collideUpdateCallBack.setAccessible(true);
        m_collideExitCallBack.setAccessible(true);
    }

    public void SetVelocity(float m_velocity)
    {
        this.m_velocity = m_velocity;
    }

    public float GetVelocity()
    {
        return m_velocity;
    }

    public void SetDir(Vector2 m_dir)
    {
        this.m_dir = m_dir.nor();
    }

    public Vector2 GetDir()
    {
        return m_dir;
    }

    public GameObject GetParent() {
        return m_parent;
    }

}
